package com.example.exceldemo.service.impl;

import cn.hutool.core.util.ObjectUtil;
import com.example.exceldemo.constant.QuotationConstant;
import com.example.exceldemo.entity.ElectricalProduct;
import com.example.exceldemo.entity.Quotation;
import com.example.exceldemo.exception.ServiceException;
import com.example.exceldemo.resp.ProductResp;
import com.example.exceldemo.service.CrossImpExcelService;
import com.example.exceldemo.utils.PoiUtils;
import com.example.exceldemo.utils.easy.ExcelUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Description
 * @Author xiaohuang
 * @Date 2025/2/19
 */
@Slf4j
@Service
public class CrossImpExcelServiceImpl implements CrossImpExcelService {
    //poi解析合并单元格，获取单元格计算公式
    @Override
    public List<String[]> poiMergeExcel(MultipartFile file) {
        List<String[]> list = readExcel(file, 0, 0);
        return list;
    }

    //读取配置文件路径
    @Value("${storage.local.path}")
    private String basePath;

    /**
     * 读取Excel文件内容
     *
     * @param excelInput Excel文件输入流
     * @param startrow   开始读取的行号
     * @param sheetnum   需要读取的sheet编号
     * @return 以字符串数组列表形式返回Excel中的数据
     */
    public static List<String[]> readExcel(MultipartFile excelInput, int startrow, int sheetnum) {
        Workbook workbook = null;
        InputStream inputStream = null;
        List<String[]> list = new ArrayList<String[]>();
        try {
            // 获取Excel文件输入流
            inputStream = excelInput.getInputStream();
            // 创建Workbook对象
            workbook = WorkbookFactory.create(inputStream);
            // 当使用路径时使用 Workbook wb = WorkbookFactory.create(new File(path));
            int numberOfSheets = workbook.getNumberOfSheets();// 获取sheet表数量
            if (numberOfSheets >= sheetnum) {
                // 获取指定编号的sheet
                Sheet sheet = workbook.getSheetAt(sheetnum);
                if (null != sheet) {
                    // 获得当前sheet的开始行
                    // int firstRowNum = sheet.getFirstRowNum();
                    // 获得当前sheet的结束行
                    int lastRowNum = sheet.getLastRowNum();
                    // 遍历sheet中的每一行，从startrow开始
                    for (int rowNum = startrow; rowNum <= lastRowNum; rowNum++) {
                        // 获得当前行
                        Row row = sheet.getRow(rowNum);
                        if (row == null) {
                            continue;
                        }
                        // 获得当前行的开始列
                        // int firstCellNum = row.getFirstCellNum();
                        // 获得当前行的列数
                        int lastCellNum = row.getLastCellNum();
                        String[] cells = new String[lastCellNum];
                        // 循环当前行的每一列
                        for (Cell c : row) {
                            // 获取当前sheet中合并单元格的数量
                            int regions = sheet.getNumMergedRegions();
                            // 判断是否存在合并单元格
                            if (regions > 0) {
                                // 判断当前单元格是否属于合并区域
                                boolean isMerge = isMergedRegion(sheet, rowNum, c.getColumnIndex());
                                if (isMerge) {
                                    // 如果是合并单元格，获取合并单元格的值
                                    String value = getMergedRegionValue(sheet, row.getRowNum(), c.getColumnIndex());
                                    cells[c.getColumnIndex()] = value;
                                } else {
                                    // 如果不是合并单元格，直接获取当前单元格的值
                                    cells[c.getColumnIndex()] = getCellValue(c);
                                }
                            } else {
                                // 如果不存在合并单元格，直接获取当前单元格的值
                                cells[c.getColumnIndex()] = getCellValue(c);
                            }
                        }
                        // 将当前行的数据添加到列表中
                        list.add(cells);
                    }
                }
            }

        } catch (Exception e1) {
            // 异常处理
            e1.printStackTrace();
        } finally {
            // 关闭Workbook和输入流
            if (null != workbook) {
                try {
                    workbook.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != inputStream) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        // 返回读取到的数据
        return list;
    }

    /**
     * 获取合并单元格的值
     * 当单元格属于合并区域时，此方法帮助获取合并区域中第一行第一列的值
     *
     * @param sheet  Excel工作表对象
     * @param row    目标单元格所在的行号
     * @param column 目标单元格所在的列号
     * @return 合并区域中第一行第一列的值，如果单元格不属于合并区域，则返回null
     */
    public static String getMergedRegionValue(Sheet sheet, int row, int column) {
        // 获取当前工作表中合并区域的数量
        int sheetMergeCount = sheet.getNumMergedRegions();
        // 遍历所有合并区域
        for (int i = 0; i < sheetMergeCount; i++) {
            // 获取当前合并区域
            CellRangeAddress ca = sheet.getMergedRegion(i);
            // 获取当前合并区域的起始列号和结束列号以及起始行号和结束行号
            int firstColumn = ca.getFirstColumn();
            int lastColumn = ca.getLastColumn();
            int firstRow = ca.getFirstRow();
            int lastRow = ca.getLastRow();
            // 检查目标单元格是否在当前合并区域内
            if (row >= firstRow && row <= lastRow) {
                if (column >= firstColumn && column <= lastColumn) {
                    // 如果目标单元格在合并区域内，获取合并区域第一行第一列的值并返回
                    Row fRow = sheet.getRow(firstRow);
                    Cell fCell = fRow.getCell(firstColumn);
                    return getCellValue(fCell);
                }
            }
        }
        // 如果目标单元格不在任何合并区域内，返回null
        return null;
    }

    /**
     * 判断给定单元格是否位于合并区域中
     *
     * @param sheet  工作表对象，用于检查合并区域
     * @param row    单元格所在的行号
     * @param column 单元格所在的列号
     * @return 如果单元格位于合并区域中，则返回true；否则返回false
     */
    private static boolean isMergedRegion(Sheet sheet, int row, int column) {
        // 获取工作表中合并区域的数量
        int sheetMergeCount = sheet.getNumMergedRegions();
        // 遍历所有合并区域
        for (int i = 0; i < sheetMergeCount; i++) {
            // 获取当前合并区域
            CellRangeAddress range = sheet.getMergedRegion(i);
            // 获取合并区域的起始列和结束列
            int firstColumn = range.getFirstColumn();
            int lastColumn = range.getLastColumn();
            // 获取合并区域的起始行和结束行
            int firstRow = range.getFirstRow();
            int lastRow = range.getLastRow();
            // 检查给定行号是否在合并区域的行范围内
            if (row >= firstRow && row <= lastRow) {
                // 检查给定列号是否在合并区域的列范围内
                if (column >= firstColumn && column <= lastColumn) {
                    // 如果行号和列号均在合并区域内，则返回true
                    return true;
                }
            }
        }
        // 如果遍历所有合并区域后未找到匹配的区域，则返回false
        return false;
    }

    //读取每个单元格的数据
    public static String getCellValue(Cell cell) {
        String cellValue = "";
        if (cell == null) {
            return cellValue;
        }
        // 判断数据的类型
        switch (cell.getCellType()) {
            case NUMERIC: // 数字 又分为纯数字和时间
                cellValue = stringDateProcess(cell);
                break;
            case STRING: // 字符串
                cellValue = java.lang.String.valueOf(cell.getStringCellValue());
                break;
            case BOOLEAN: // Boolean
                cellValue = java.lang.String.valueOf(cell.getBooleanCellValue());
                break;
            case FORMULA: // 公式
                //获取到公式本身
                cellValue = java.lang.String.valueOf(cell.getCellFormula());
                //获取到公式的值
                try {
                    cellValue = java.lang.String.valueOf(cell.getNumericCellValue());
                } catch (Exception e) {
                    cellValue = java.lang.String.valueOf(cell.getRichStringCellValue());
                }

                break;
            case BLANK: // 空值
                cellValue = "";
                break;
            case ERROR: // 故障
                cellValue = "非法字符";
                break;
            default:
                cellValue = "无";
                break;
        }
        return cellValue;
    }

    /**
     * 时间格式转换
     *
     * @param cell
     * @return
     */
    public static String stringDateProcess(Cell cell) {
        String result = "";
        if (HSSFDateUtil.isCellDateFormatted(cell)) {// 处理日期格式、时间格式
            SimpleDateFormat sdf = null;
            if (cell.getCellStyle().getDataFormat() == HSSFDataFormat.getBuiltinFormat("h:mm")) {
                sdf = new SimpleDateFormat("HH:mm ");
            } else {// 日期
                sdf = new SimpleDateFormat("yyyy-MM-dd");
            }
            Date date = cell.getDateCellValue();
            result = sdf.format(date);
        } else if (cell.getCellStyle().getDataFormat() == 58) {
            // 处理自定义日期格式：m月d日(通过判断单元格的格式id解决，id的值是58)
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
            double value = cell.getNumericCellValue();
            Date date = org.apache.poi.ss.usermodel.DateUtil.getJavaDate(value);
            result = sdf.format(date);
        } else {
            double value = cell.getNumericCellValue();
            CellStyle style = cell.getCellStyle();
            DecimalFormat format = new DecimalFormat();
            String temp = style.getDataFormatString();
            // 单元格设置成常规
            if (temp.equals("General")) {
                format.applyPattern("#");
            }
            result = format.format(value);
        }
        return result;
    }


    public ProductResp saveExcelInfo(Map<Integer, List<LinkedHashMap>> sheetInfos) {
        //存储不同sheet实体信息
        ProductResp productResp = new ProductResp();
        for (Integer sheetNo : sheetInfos.keySet()) {
            List<LinkedHashMap> maps = sheetInfos.get(sheetNo);
            // 不同sheet页数据处理方式不同
            switch (sheetNo) {
                case 0:
                    settlementMonthlyReport(maps, productResp);
                    break;
                default:
                    break;
            }
        }
        return productResp;
    }

    private void settlementMonthlyReport(List<LinkedHashMap> maps, ProductResp productResp) {
        // 基本资料
        ElectricalProduct baseInfo = new ElectricalProduct();
        // 报价表明细
        List<LinkedHashMap> quotationDetails = new ArrayList<>();
        // 是否基本资料
        boolean isBaseInfo = true;
        // 是否报价表
        boolean isQuotation = false;

        for (LinkedHashMap map : maps) {
            if (isBaseInfo) {
                // 基本资料
                isBaseInfo = dealBaseInfo(map, baseInfo);
                if (!isBaseInfo)
                    continue;
            }
            // “报价表”-表格标题：合并单元格，数值在左上角
            Object obj = map.get("0");
            String str = null;
            if (ObjectUtil.isNotEmpty(obj))
                str = obj.toString().replaceAll("\\p{Z}", "");
            // 判断是否是“报价表”
            if (StringUtils.isNotBlank(str) && str.contains(QuotationConstant.QUOTATION_TABLE_TITLE))
                isQuotation = true;

            if (!isBaseInfo && isQuotation)
                // 报价表-表格
                isQuotation = dealQuotationDetails(map, quotationDetails, str);

        }

        if (!quotationDetails.isEmpty()) {
            // 将从Excel中读取的数据转换为指定类型的对象列表
            List<Quotation> dtos = ExcelUtils.convertList(quotationDetails, Quotation.class);
            productResp.setQuotations(dtos);
        }
        productResp.setProduct(baseInfo);
    }

    private boolean dealBaseInfo(LinkedHashMap map, ElectricalProduct baseInfo) {
        // 是否基础信息内容
        //基本信息
        if (map.containsValue(QuotationConstant.BASE_INFO_TITLE))
            return true;

        // 根据表格内容分析，对象值在2,9列。合并单元格在读取之后会将值存在合并的第一列索引下，如合并A-C列，那A列会存储字段值，B、C列值为空。
        Object obj = map.get("2");
        String str = ObjectUtil.isNotEmpty(obj) ? obj.toString() : null;
        Object object = map.get("9");
        String value = ObjectUtil.isNotEmpty(object) ? object.toString() : null;

        // 通过本行的第一个字段名，获取本行的所有字段信息(后面的判断均是如此，常量类对应了各个字段名)
        //询价单位
        if (map.containsValue(QuotationConstant.QUOTE_UNIT)) {
            baseInfo.setUnit(str);
            baseInfo.setContact(value);
        }
        //项目名称
        if (map.containsValue(QuotationConstant.PROJECT_NAME)) {
            baseInfo.setProjectName(str);
            baseInfo.setContactWay(value);
        }
        //项目地址
        if (map.containsValue(QuotationConstant.PROJECT_ADDRESS)) {
            baseInfo.setProjectAddress(str);
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日");
            baseInfo.setQuoteTime(LocalDate.parse(value, formatter));
        }
        //报价单位
        if (map.containsValue(QuotationConstant.QUOTE_UNIT)) {
            baseInfo.setQuoteUnit(str);
            baseInfo.setQuotePerson(value);
            // 基本信息赋值完
            return false;
        }
        return true;
    }

    private Boolean dealQuotationDetails(LinkedHashMap map, List<LinkedHashMap> quotationDetails, String firstStr) {
        // 确定模块结束标识（“合   计（大写）”）
        if (StringUtils.isNotBlank(firstStr) && firstStr.contains(QuotationConstant.TOTAL_ROW_TITLE))
            return false;

        // 模块开始标识为模块标题("报价表")以及模块第一行字段名（“序号”），所以读取数据时要排除这两行
        if (StringUtils.isNotBlank(firstStr) && !map.containsValue(QuotationConstant.SEQUENCE_NUMBER) && !firstStr.contains(QuotationConstant.QUOTATION_TABLE_TITLE)) {
            quotationDetails.add(map);
        }
        return true;
    }
    /**
     * 读取Excel文件中的图片
     *
     * @param file 要读取的Excel文件
     * @param sheetNo 工作表编号
     *
     * 此方法主要用于从Excel文件中读取图片，并根据文件后缀名选择合适的处理方式
     * 它通过Apache POI库来解析Excel文件，并提取指定工作表中的图片数据
     */
    @Override
    public void poiReadImg(MultipartFile file, Integer sheetNo) {
        Workbook wb = null;
        InputStream inputStream = null;
        try {
            // 获取文件后缀名
            String fileExt = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".") + 1);
            inputStream = file.getInputStream();
            // 创建Workbook对象
            wb = WorkbookFactory.create(inputStream);
            Sheet sheet = wb.getSheetAt(sheetNo);
            // 获取最后一行序号,从零开始
            int lastRowNum = sheet.getLastRowNum();
            // 存储图片信息和坐标
            Map<String, List<PictureData>> picMap = new HashMap<>();
            // 判断用07还是03的方法获取图片
            if (fileExt.equals("xls"))
                picMap = PoiUtils.getSheetPicturesBatch03(null, (HSSFSheet) sheet, (HSSFWorkbook) wb);
            else
                picMap = PoiUtils.getSheetPicturesBatch07(null, (XSSFSheet) sheet, (XSSFWorkbook) wb);
            for (int i = 1; i <= lastRowNum; i++) {
                Row row = sheet.getRow(i);
                String name = row.getCell(0) == null ? "" : row.getCell(0).getStringCellValue();
                String sex = row.getCell(1) == null ? "" : row.getCell(1).getStringCellValue();
                // 获取图片数据
                List<PictureData> pictureDataList = picMap.get(i + "," + 2);
                String paths = initImage(pictureDataList);
                System.out.println("名称:" + name + ",    性别:" + sex + ",    图片存储路径:" + paths);
            }
        } catch (IOException e) {
            throw new ServiceException("读取图片异常");
        } finally {
            try {
                if (inputStream != null)
                    inputStream.close();
                if (wb != null)
                    wb.close();
            } catch (IOException e) {
                throw new ServiceException("关闭流异常");

            }
        }
    }

    /**
     * 初始化图片数据
     * 该方法接收一个图片数据列表，并为每张图片生成一个唯一的文件名，然后返回所有图片的文件路径字符串
     * 如果图片数据列表为空，则返回空字符串
     * 注意：该方法目前并未实现实际的图片文件写入操作
     *
     * @param pictureDataList 图片数据列表
     * @return 所有图片的文件路径字符串
     */
    public   String initImage(List<PictureData> pictureDataList) {
        String paths = "";
        if (CollectionUtils.isEmpty(pictureDataList))
            return paths;
        for (PictureData pictureData : pictureDataList) {
            String suggestFileExtension = pictureData.suggestFileExtension(); // 图片格式
            String fileName = UUID.randomUUID().toString() + "." + suggestFileExtension;
            String path = basePath + fileName; // 存储路径
            if (StringUtils.isNotBlank(paths))
                paths = String.join(",", paths, fileName);
            else
                paths = path;
//            try (FileOutputStream out = new FileOutputStream(path)) {
//                out.write(pictureData.getData());
//            } catch (IOException e) {
//                throw new ServiceException("Failed to write image file: " + path, e);
//            }
        }
        return paths;
    }


}
